package org.example.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.example.entity.*;
import org.example.entity.vo.LoginVO;
import org.example.exception.LoginException;
import org.example.security.UserDetailsImpl;
import org.example.service.IMenuService;
import org.example.service.IRoleService;
import org.example.service.IUserService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class TokenService {
    private final IUserService userService;
    private final IRoleService roleService;
    private final IMenuService menuService;
    private final StringRedisTemplate stringRedisTemplate;
    private final ObjectMapper objectMapper;
    private final RedisTemplate redisTemplate;

    private static final String LOGIN_KEY = "s54:rbac:login:";
    /**
     * 1、根据登录名查询用户
     * 2、比对密码
     * 3、比对状态
     * 4、获取用户的角色列表
     * 5、根据角色获取用户的权限列表
     * 6、生成token，存储用户及角色、权限到redis
     * @param loginVO
     * @return
     */
    @SneakyThrows
    public Token createToken(LoginVO loginVO) {
        // 1.
        User user = userService.getOne(Wrappers.lambdaQuery(User.class).eq(User::getUserName, loginVO.getLoginName()));
        if (user == null) throw new LoginException("用户不存在");
        if (!user.getPassword().equals(loginVO.getPassword())) throw new LoginException("密码不正确");
        if (!user.getStatus().equals("0")) throw new LoginException("用户未激活");
        // 2. 获取用户所有的角色列表
        List<Role> roleList = roleService.listByUserId(user.getUserId());
        // 3. 根据角色获取所有菜单
        List<Menu> menuList = menuService.listByRoleIds(roleList.stream().mapToLong(Role::getRoleId).boxed().toArray(Long[]::new));
        // 4. 存储到redis
        String uuid = UUID.randomUUID().toString();
        Set<String> roleNames = new HashSet<>();
        for (Role role : roleList) {
            roleNames.add(role.getRoleKey());
        }

        Set<String> perms = new HashSet<>();
        for (Menu menu : menuList) {
            if (StringUtils.hasText(menu.getPerms())) {
                perms.add(menu.getPerms());
            }
        }

        LoginUser loginUser = new LoginUser(roleNames, perms, user);
        String json = new ObjectMapper().writeValueAsString(loginUser);
        stringRedisTemplate.opsForValue().set(LOGIN_KEY + uuid, json, Duration.ofMinutes(30));
        return new Token(System.currentTimeMillis(), System.currentTimeMillis() + 1800000, uuid, user.getNickName());
    }

    public void removeToken(String token) {
        stringRedisTemplate.delete(LOGIN_KEY + token);
    }

    @SneakyThrows
    public boolean hasPerms(String perms) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String token = requestAttributes.getRequest().getHeader("token");
        if (StringUtils.isEmpty(token)) return false;

        String json = stringRedisTemplate.opsForValue().get(LOGIN_KEY + token);
        if (StringUtils.isEmpty(json)) return false;

        LoginUser loginUser = new ObjectMapper().readValue(json, LoginUser.class);
        Set<String> permsAndRoles = new HashSet<>();
        permsAndRoles.addAll(loginUser.getPerms());
        permsAndRoles.addAll(loginUser.getRoleNames());

        return permsAndRoles.contains(perms);
    }

    @SneakyThrows
    public Token createToken(UserDetailsImpl authenticate) {
        // 4. 存储到redis
        String uuid = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(LOGIN_KEY + uuid, authenticate, Duration.ofMinutes(30));
        return new Token(System.currentTimeMillis(), System.currentTimeMillis() + 1800000, uuid, authenticate.getUser().getNickName());
    }

    @SneakyThrows
    public UserDetailsImpl getLoginUser(String token) {
        return (UserDetailsImpl) redisTemplate.opsForValue().get(LOGIN_KEY + token);
    }
}
